<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <input type="text" v-model="message">
        {{message}}
    </div>
    <script>
        class Vue {
            constructor(options) {
                //1.保存数据
                this.$options = options;
                this.$data = options.data;
                this.$el = options.el;

                //2.将data添加到响应式系统中
                new Observer(this.$data);

                //3.代理this.$data数据
                Object.keys(this.$data).forEach(key => {
                    this._proxy(key);
                })

                //4.处理el
                new Compiler(this.$el, this)
            }

            _proxy(key) {
                Object.defineProperty(this, key, {
                    configurable: true,
                    enumerable: true,
                    set(newValue) {
                        this.$data[key] = newValue;
                    },
                    get() {
                        return this.$data[key]
                    }
                })
            }
        }

        class Observer {
            constructor(data) {
                this.data = data
                Object.keys(data).forEach(key => {
                    this.defineReactive(this.data, key, data[key])
                })
            }

            defineReactive(data, key, val) {
                const dep = new Dep()
                Object.defineProperty(data, key, {
                    configurable: true,
                    enumerable: true,
                    get() {
                        if (Dep.target) {
                            dep.addSub(Dep.target)
                        }
                        return val;
                    },
                    set(newValue) {
                        if (newValue === val) {
                            return;
                        }
                        val = newValue;
                        dep.notify();
                    }
                })
            }
        }

        class Dep {
            constructor() {
                this.subs = []
            }
            addSub(sub) {
                this.subs.push(sub)
            }
            notify() {
                this.subs.forEach(sub => {
                    sub.update()
                })
            }
        }

        class Watch {
            constructor(node, name, vm) {
                this.node = node;
                this.name = name;
                this.vm = vm;
                Dep.target = this;
                this.update();
                Dep.target = null;
            }

            update() {
                this.node.newValue = this.vm[this.name];
            }
        }

        const reg = /\{\{(.*)\}\}/
        class Compiler {
            constructor(el, vm) { //el==>'#app',vm==>Vue实例
                this.el = document.querySelector(el)
                this.vm = vm
                this.frag = this._createFragment()
                this.el.appendChild(this.frag)
            }

            _createFragment() {
                const frag = document.createDocumentFragment()

                let child;
                while (child = this.el.firstChild) {
                    this._compile(child)
                    frag.appendChild(child)
                }

                return frag;
            }

            _compile(node) { //节点解析，例子：<h2>{{message}}</h2>
                if (node.nodeType === 1) { //假如是个标签节点
                    const attrs = node.attributes;
                    if (attrs.hasOwnProperty('v-model')) {
                        const name = attrs['v-model'].nodeValue;
                        node.addEventListener('input', e => {
                            this.vm[name] = e.target.value;
                        })
                    }
                }

                if (node.nodeType === 3) { //假如时文本节点
                    console.log(reg.test(node.nodeValue));
                    if (reg.test(node.nodeValue)) {
                        const name = RegExp.$1.trim()
                        console.log(name)
                        new Watch(node, name, this.vm)
                    }
                }
            }
        }
    </script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                message: '你好啊',
                info: {
                    name: 'zhangh',
                    age: 18
                }
            }
        })
    </script>
</body>

</html>